/// <reference path="./BlockController.ts" />
/// <reference path="./StateManager.ts" />
/// <reference path="./Timer.ts" />
/// <reference path="./InfoPanel.ts" />
/// <reference path="./CommandPanel.ts" />
/// <reference path="./Puzzle.ts" />

namespace tetris {
    export interface ITetrisOptions {
        width?: number;
        height?: number;
        puzzle?: JQuery | string;
        preview?: JQuery | string;
        restart?: JQuery | string;
        pause?: JQuery | string;
        score?: JQuery | string;
        speed?: JQuery | string;
        initialSpeed: number;
    }

    const DEFAULT_OPTIONS: ITetrisOptions = {
        width: 10,
        height: 18,
        puzzle: "#puzzle",
        preview: "#preview",
        restart: "#restart",
        pause: "#pause",
        score: "#score",
        speed: "#speed",
        initialSpeed: 5
    };

    export class Tetris {
        private _options: ITetrisOptions;
        private _blockController: BlockController;
        private _timer: Timer;
        private _states: StateManager;
        private _puzzle: Puzzle;
        private _preview: Puzzle;
        private _infoPanel: InfoPanel;
        private _commandPanel: CommandPanel;

        constructor(_options?: ITetrisOptions) {
            const options = this._options = $.extend({}, DEFAULT_OPTIONS, _options)
            this._puzzle = new Puzzle(options.width, options.height);
            this._puzzle.build($(options.puzzle));
            this._preview = new Puzzle(4, 4);
            this._preview.build($(options.preview));

            this._blockController = new BlockController(options.width, options.height);
            this._commandPanel = new CommandPanel(options.restart, options.pause);

            this._infoPanel = new InfoPanel(options.score, options.speed);
            this._infoPanel.speed = options.initialSpeed;

            this._states = new StateManager();
            this._timer = new Timer(this._infoPanel.interval);

            this.setup();
        }

        private setup() {
            this.setupEvents();
            this.setupKeyEvents();
        }

        private setupEvents() {
            this._blockController
                .on("reset", () => {
                    this._infoPanel.score = 0;
                    this._timer.interval = this._infoPanel.interval;
                })
                .on("nextChanged", (event: IEvent) => {
                    this._preview.block = event["block"];
                    this._preview.render();
                })
                .on("currentChanged", (event: IEvent) => {
                    this._puzzle.block = event["block"];
                    this._preview.render();
                })
                .on("fastening", this._states.pause.bind(this._states, ByWhat.CODE))
                .on("fastened", () => {
                    this._states.resume(ByWhat.CODE);
                })
                .on("erasedRows", (event: IEvent) => {
                    this._infoPanel.addByLevel(event["rowsCount"]);
                    this._puzzle.fastened = this._blockController.fastenMatrix();
                    this._puzzle.block = null;
                })
                .on("gameover", this._states.over.bind(this._states))
                .on("render", (event: IEvent) => {
                    if (event["shouldFasten"]) {
                        this._puzzle.fastened = this._blockController.fastenMatrix();
                    }
                    this._puzzle.render();
                });

            this._timer
                .on("performed", this._blockController.process.bind(this._blockController));

            this._states
                .on("pause", this._timer.stop.bind(this._timer))
                .on("resume", this._timer.restart.bind(this._timer))
                .on("restart", this._blockController.restart.bind(this._blockController));

            this._infoPanel
                .on("speedUp", (event: IEvent) => {
                    this._timer.interval = event["interval"];
                });

            this._commandPanel
                .on("pause", () => {
                    if (this._states.isPausedByManual) {
                        this._states.resume(ByWhat.MANUAL);
                    } else {
                        this._states.pause(ByWhat.MANUAL);
                    }
                })
                .on("restart", this._states.restart.bind(this._states));
        }

        private setupKeyEvents() {
            const controller = this._blockController;

            const handlers = {
                // up
                "38": controller.rotate.bind(controller),

                // left
                "37": controller.moveLeft.bind(controller),

                // right
                "39": controller.moveRight.bind(controller),

                // down
                "40": controller.moveDown.bind(controller),

                // fall down to bottom
                "32": controller.fallDownDirectly.bind(controller),
            };

            $(document).on("keydown", e => {
                if (this._states.isPaused) {
                    return;
                }
                const handler = handlers[e.keyCode];
                if (handler) {
                    handler();
                }
            });
        }

        run() {
            this._blockController.run();
            this._timer.restart();
        }
    }
}
